#if TOOLS
using Godot;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Janphe.addons.Avatar
{
    [Tool]
    public class Plugin_RestructSkeleton
    {

        internal static bool selected(EditorInterface editor)
        {
            var selected = editor.GetSelection().GetSelectedNodes();
            if (selected.Count != 1)
            {
                GD.Print($"请选择 1 个骨架节点，当前选择了 {selected.Count} 个节点。");
                foreach (Node p in selected)
                {
                    GD.Print($"{p.Name} {p.GetType().Name}");
                }
                return false;
            }

            var source = selected[0] as Skeleton;
            if (source == null)
            {
                GD.Print($"请选择 {typeof(Skeleton)} 节点，当前选择了 {selected[0].GetType()} 节点。");
                return false;
            }

            return true;
        }

        private static int rightOrLeft(string lor)
        {
            var lfts = new string[] { "left", "_l", "l_" };
            if (Array.Exists(lfts, s => lor.Contains(s)))
                return -1;

            var rhts = new string[] { "right", "righ_", "_r", "r_" };
            if (Array.Exists(rhts, s => lor.Contains(s)))
                return 1;

            return 0;
        }

        private static Spatial findBone(Spatial start, string dst, string lor = "")
        {
            if (start.Visible && start.Name.Contains(dst))
            {
                if (string.IsNullOrEmpty(lor))
                {
                    start.Visible = false;
                    return start;
                }

                var a = rightOrLeft(start.Name.ToLower());
                var b = rightOrLeft(lor);

                if (a == b)
                {
                    start.Visible = false;
                    return start;
                }
            }

            foreach (Spatial child in start.GetChildren())
            {
                var b = findBone(child, dst, lor);
                if (b != null)
                    return b;
            }
            return null;
        }

        internal static Bone[] MatchBones(string[] boneNames)
        {
            var dict = boneNames.Select((b, i) => new KeyValuePair<int, string>(i, b)).ToArray();
            Array.Sort(dict, (a, b) => a.Value.CompareTo(b.Value));

            var avatar = (Spatial)GD.Load<PackedScene>("addons/Avatar/Pose-T.tscn").Instance();

            var section = "tags";
            var tagcfg = new ConfigFile();
            tagcfg.Load("addons/Avatar/Pose-T.tags");
            var keyS = tagcfg.GetSectionKeys(section);

            var keyV = new Dictionary<string, string>();
            foreach (var s in keyS)
                keyV.Add(s, tagcfg.GetValue(section, s) as string);


            var used = new Dictionary<string, Spatial>();

            for (var i = 0; i < dict.Length; ++i)
            {
                var name = dict[i].Value;

                var src_s = name.ToLower();
                var dst_S = "";
                var lor = false;

                foreach (var key in keyS)
                {
                    var key_s = key.ToLower();
                    if (src_s.Contains(key_s))
                    {
                        dst_S = key;
                        lor = keyV[key].Contains(",lr");
                        break;
                    }
                }
                if (!string.IsNullOrEmpty(dst_S))
                {
                    var bone = findBone(avatar, dst_S, lor ? src_s : "");
                    if (bone == null)
                    {
                        GD.Print($"{i} {name} => null <= {dst_S} {lor}");
                    }
                    else
                    {
                        //GD.Print($"{i:D2} {name,-16} => {bone.Name,-16} <= {dst_S,-16} {lor,-16}");
                    }
                    used[name] = bone;
                }
            }

            for (var i = 0; i < dict.Length; ++i)
            {
                var name = dict[i].Value;

                if (used.ContainsKey(name))
                    continue;

                var src_s = name.ToLower();
                var dst_S = "";
                var lor = false;
                foreach (var _S in keyS)
                {
                    var obj = tagcfg.GetValue(section, _S) as string;
                    var tags = obj.Split(",").Where(s => !string.IsNullOrEmpty(s)).Select(s => s.ToLower());

                    if (tags.Any(s => src_s.Contains(s)))
                    {
                        dst_S = _S;
                        lor = keyV[_S].Contains(",lr");
                        break;
                    }
                }

                if (!string.IsNullOrEmpty(dst_S))
                {
                    var bone = findBone(avatar, dst_S, src_s);
                    if (bone == null)
                    {
                        GD.Print($"{i} {name} => null <= {dst_S} {lor}");
                    }
                    else
                    {
                        //GD.Print($"{i:D2} {name,-16} => {bone.Name,-16} <= {dst_S,-16} {lor,-16}");
                    }
                    used[name] = bone;
                }
                else
                {
                    GD.Print($"{i} {name} => empty!!!!!!");
                }
            }

            var bones = new Bone[boneNames.Length];

            for (var i = 0; i < bones.Length; ++i)
            {
                var name = boneNames[i];
                var bone = bones[i] = new Bone();
                bone.i = i;
                bone.node = used[name];
                bone.name = bone.node.Name;
                bone.rest = bone.node.Transform;
                bone.pose = Transform.Identity;
            }
            for (var i = 0; i < bones.Length; ++i)
            {
                var bone = bones[i];
                var pid = findParent(bone.node);
                bone.p = pid;

                int findParent(Spatial node)
                {
                    var p = node.GetParent<Spatial>();
                    if (p == null)
                        return -1;

                    var idx = Array.FindIndex(bones, b => b.node == p);
                    if (idx >= 0)
                        return idx;

                    return findParent(p);
                }
            }

            avatar.Free();

            return bones;
        }

        internal static void Skeleton2Nodes(EditorInterface editor)
        {
            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;

            var owner = editor.GetEditedSceneRoot();

            var bones = new Spatial[skeleton.GetBoneCount()];
            for (var i = 0; i < bones.Length; ++i)
            {
                var name = skeleton.GetBoneName(i);
                bones[i] = new Spatial() { Name = name };

                if (string.IsNullOrEmpty(name))
                    GD.PrintErr($"empty boneIdx:{i}");


                var pi = skeleton.GetBoneParent(i);
                if (pi < 0)
                {
                    if (skeleton.HasNode(name))
                    {
                        bones[i] = skeleton.GetNode(name) as Spatial;
                    }
                    else
                    {
                        skeleton.AddChild(bones[i]);
                    }
                }
                bones[i].SetMeta("boneIdx", i);
            }


            for (var i = 0; i < bones.Length; ++i)
            {

                var pi = skeleton.GetBoneParent(i);
                if (pi < 0)
                    continue;

                var bone = bones[i];
                if (bones[pi].HasNode(bone.Name))
                    continue;

                bones[pi].AddChild(bone);
            }

            for (var i = 0; i < bones.Length; ++i)
            {
                var bone = bones[i];
                bone.Transform = skeleton.GetBoneRest(i);

                if (bone.Owner != null)
                    continue;
                if (owner.IsAParentOf(bone))
                    bone.Owner = owner;
            }

            var filePath = owner.Filename;
            editor.OpenSceneFromPath(filePath);
        }

        internal static void Nodes2Config(EditorInterface editor)
        {
            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;
            var sf = skeleton.Filename;
            if (string.IsNullOrEmpty(sf))
            {
                GD.Print($"skeleton.Filename empty! 请把Skeleton单独保存成资源。");
                return;
            }

            var config = new ConfigFile();

            void bb(Spatial b)
            {
                config.SetValue("bones", b.Name, $"");
                foreach (Spatial c in b.GetChildren())
                    bb(c);
            }

            bb(skeleton);
            config.Save($"{sf}.cfg");
        }

        internal static void Nodes2Skeleton(EditorInterface editor)
        {
            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;

            skeleton.ClearBones();

            var boneIdx = -1;
            void bb(Spatial b, int p)
            {
                boneIdx++;
                skeleton.AddBone(b.Name);

                skeleton.SetBoneParent(boneIdx, p);
                //skeleton.SetBoneRest(boneIdx, b.Transform);//静止骨骼矩阵
                //skeleton.SetBonePose(boneIdx, Transform.Identity);  //动画骨骼矩阵

                skeleton.SetBoneRest(boneIdx, Transform.Identity);//静止骨骼矩阵
                skeleton.SetBonePose(boneIdx, b.Transform);  //动画骨骼矩阵

                //skeleton.SetBoneParent(boneIdx, boneIdx > 0 ? 0 : -1);
                //skeleton.SetBonePose(boneIdx, b.GlobalTransform);

                p = boneIdx;
                foreach (Spatial c in b.GetChildren())
                    bb(c, p);
            }

            foreach (Spatial c in skeleton.GetChildren())
                bb(c, -1);

            //int cc(Node node)
            //{
            //    var c = node.GetChildCount();
            //    foreach (Node child in node.GetChildren())
            //        c += cc(child);
            //    return c;
            //}
            //var boneCount = cc(skeleton);
        }

        internal static void ForEach<T>(T d, Action<T> callback) where T : Node
        {
            callback(d);
            foreach (T c in d.GetChildren())
                ForEach(c, callback);
        }

        internal static void ResortSkeleton2(EditorInterface editor)
        {
            // 需要排序处理
            GD.Print("\nResortSkeleton...");

            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;

            var boneNames = new string[skeleton.GetBoneCount()];
            for (var i = 0; i < boneNames.Length; ++i)
                boneNames[i] = skeleton.GetBoneName(i);

            var poseT = new Skeleton();
            var bones = MatchBones(boneNames);
            for (var i = 0; i < bones.Length; ++i)
            {
                var rest = skeleton.GetBoneRest(i);
                var pose = skeleton.GetBonePose(i);

                var bone = bones[i];

                //var trans = bone.b.Transform;
                //trans.origin = trans.origin.Normalized() * rest.origin.Length();

                poseT.AddBone(bone.name);
                poseT.SetBoneRest(i, rest);
                poseT.SetBonePose(i, pose);
                poseT.SetBoneParent(i, bone.p);

            }

            skeleton.AddChild(poseT);
            poseT.Owner = skeleton;
        }

        internal static void Dis_Rest_OrNot(EditorInterface editor)
        {
            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;

            for (var i = 0; i < skeleton.GetBoneCount(); ++i)
            {
                skeleton.SetBoneDisableRest(i, !skeleton.IsBoneRestDisabled(i));
            }
        }

        internal static void Rest_2_Pose(EditorInterface editor)
        {
            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;

            for (var i = 0; i < skeleton.GetBoneCount(); ++i)
            {
                skeleton.SetBonePose(i, skeleton.GetBoneRest(i));
                skeleton.SetBoneRest(i, Transform.Identity);
            }
        }

        internal static void Pose_2_Rest(EditorInterface editor)
        {
            if (!selected(editor))
                return;
            var skeleton = editor.GetSelection().GetSelectedNodes()[0] as Skeleton;

            for (var i = 0; i < skeleton.GetBoneCount(); ++i)
            {
                skeleton.SetBoneRest(i, skeleton.GetBonePose(i));
                skeleton.SetBonePose(i, Transform.Identity);
            }
        }
    }
}
#endif
